#pragma once

#include <boost/functional/hash.hpp>
#include <boost/operators.hpp>
#include <boostext/typedefs.hpp>

#include "math_exports.hpp"




namespace math
{
	template <typename T, typename S = void>
	class size2
		: boost::addable< size2<T, S>
		, boost::subtractable< size2<T, S>
		, boost::multiplicative< size2<T, S>, T
		> > >
	{
	public:

		T width;
		T height;

	public:

		/**
		 * Creates a size with a width and height of 0.
		 */
		size2()
			: width(0)
			, height(0)
		{}

		/**
		 * Creates a size with the given width and height.
		 */
		size2(T width, T height)
			: width(width)
			, height(height)
		{}



		/**
		 * Test if this size is normalized or not.
		 */
		bool isNormalized() const
		{
			return width >= 0 && height >= 0;
		}

		/**
		 * Normalizes this size and returns a reference to itself.
		 */
		size2<T, S>& normalize()
		{
			if(width < 0)  width = -width;
			if(height < 0) height = -height;

			return *this;
		}

		/**
		 * Returns a normalized version of this size.
		 */
		size2<T, S> normalized() const
		{
			size2<T, S> tmp = *this;
			tmp.normalize();
			return tmp;
		}



		/**
		 * Returns the area of this size.
		 * The area is always positive, even when this rectangle is
		 * not normalized.
		 */
		T area() const
		{
			T w = width;
			T h = height;

			if(w < 0) w = -w;
			if(h < 0) h = -h;

			return w * h;
		}


		/**
		 * Adds the given size to this one.
		 */
		size2<T, S>& operator+=(size2<T, S> that)
		{
			width  += that.width;
			height += that.height;

			return *this;
		}

		/**
		 * Subtracts the given size from this one.
		 */
		size2<T, S>& operator-=(size2<T, S> that)
		{
			width  -= that.width;
			height -= that.height;

			return *this;
		}

		/**
		 * Scales this size by the given factor.
		 */
		size2<T, S>& operator*=(T scale)
		{
			width  *= scale;
			height *= scale;

			return *this;
		}

		/**
		 * Scales this size by the given factor.
		 */
		size2<T, S>& operator/=(T scale)
		{
			width  /= scale;
			height /= scale;

			return *this;
		}
	};
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * Returns a normalized version of this rectangle.
	 */
	template <typename T, typename S>
	size2<T, S> normalize(size2<T, S> that)
	{
		return that.normalized();
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * This function performs a component-wise comparison and returns a new vector,
	 * composed of the lowest input values.
	 *
	 * @returns vector with the component wise minimum values
	 */
	template <typename T>
	size2<T> min(const size2<T>& a, const size2<T>& b)
	{
		return size2<T>(std::min(a.width, b.width), std::min(a.height, b.height));
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * This function performs a component-wise comparison and returns a new vector,
	 * composed of the highest input values.
	 *
	 * @returns vector with the component wise maximum values
	 */
	template <typename T>
	size2<T> max(const size2<T>& a, const size2<T>& b)
	{
		return size2<T>(std::max(a.width, b.width), std::max(a.height, b.height));
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * Calculate a hash for a 2-dimensional rectangle.
	 */
	template <typename T, typename S>
	std::size_t hash_value(const size2<T, S>& that)
	{
		std::size_t seed = 0;
		boost::hash_combine(seed, that.width);
		boost::hash_combine(seed, that.height);
		return seed;
	}
	///////////////////////////////////////////////////////////////////////////////////////////////



	/**
	 * In order to compile a binary that actually contains the code for our vectors,
	 * we have to explicitly instantiate the template vectors and export those:
	 */
	template class MATH_API size2<int32>;
	template class MATH_API size2<float>;
	template class MATH_API size2<double>;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
